#pragma once

#include <string>
#include <fstream>
#include <vector>
#include <stdexcept>
#include <io.h>
#include "String.hpp"

namespace PokerBoxUtils
{
    using std::ifstream;
    using std::string;
    using std::vector;

    class CsvItem;
    class CsvRow;

    class FileNotFoundException : public std::runtime_error
    {
    public:
        FileNotFoundException(const std::string &fileName)
            : std::runtime_error("File " + fileName + " is not exists.") {}
    };

    class FileOpenException : public std::runtime_error
    {
    public:
        FileOpenException(const std::string &fileName)
            : std::runtime_error("Cann't open file " + fileName + ".") {}
    };

    class CsvItem
    {
    private:
        int _rowIndex;
        int _columnIndex;
        string _content;

    public:
        inline int GetRowIndex() const
        {
            return _rowIndex;
        }

        inline int GetColumnIndex() const
        {
            return _columnIndex;
        }

        inline const string &GetContent()
        {
            return _content;
        }

        CsvItem(int rowIndex, int columnIndex, std::string &content)
        {
            _rowIndex = rowIndex;
            _columnIndex = columnIndex;
            _content = content;
        }
    };

    class CsvRow
    {
    private:
        int _columnCount;

        vector<CsvItem> _rowItems;

    public:
        int GetColumnCount() const
        {
            return _columnCount;
        }

        CsvItem &GetCsvItem(int columnIndex)
        {
            if (columnIndex >= _columnCount || columnIndex < 0)
            {
                throw std::out_of_range("Index is out of range.");
            }

            return _rowItems[columnIndex];
        }

        CsvRow(vector<CsvItem> &items)
        {
            _rowItems = items;
            _columnCount = items.size();
        }
    };

    class CsvReader
    {
    private:
        string _fileName;

        vector<CsvRow> *_rows;

    public:
        ~CsvReader()
        {
            if (_rows != nullptr)
            {
                delete _rows;
            }
        }

        inline int GetRowCount()
        {
            return GetRows().size();
        }

        inline vector<CsvRow> &GetRows()
        {
            if (_rows == nullptr)
            {
                _rows = new vector<CsvRow>();

                if (access(_fileName.c_str(), 0))
                {
                    throw FileNotFoundException(_fileName);
                }

                ifstream fstream(_fileName);

                if (!fstream.is_open())
                {
                    throw FileNotFoundException(_fileName);
                }

                string line;
                auto rowIndex = 0;

                while (std::getline(fstream, line))
                {
                    auto strList = String::Split(line, ',');
                    auto newRow = vector<CsvItem>();
                    auto columnIndex = 0;

                    for (auto &strList : strList)
                    {
                        auto csvItem = CsvItem(rowIndex, columnIndex, strList);
                        newRow.push_back(csvItem);
                        columnIndex++;
                    }

                    _rows->push_back(newRow);
                    rowIndex++;
                }

                fstream.close();
            }

            return *_rows;
        }

        CsvReader(const string &fileName)
        {
            _rows = nullptr;
            _fileName = fileName;
        }
    };
}